// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include "kudu/server/pprof_path_handlers.h"

#include <unistd.h>

#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <gflags/gflags_declare.h>
#include <glog/logging.h>
#ifdef TCMALLOC_ENABLED
#include <gperftools/heap-profiler.h>
#include <gperftools/malloc_extension.h>
#include <gperftools/profiler.h>
#endif

#include "kudu/gutil/map-util.h"
#include "kudu/gutil/strings/numbers.h"
#include "kudu/gutil/strings/split.h"
#include "kudu/gutil/strings/stringpiece.h"
#include "kudu/gutil/strings/strip.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/sysinfo.h"
#include "kudu/server/webserver.h"
#include "kudu/util/env.h"
#include "kudu/util/faststring.h"
#include "kudu/util/monotime.h"
#include "kudu/util/spinlock_profiling.h"
#include "kudu/util/status.h"
#include "kudu/util/web_callback_registry.h"

DECLARE_bool(enable_process_lifetime_heap_profiling);
DECLARE_string(heap_profile_path);


using std::endl;
using std::ifstream;
using std::ostringstream;
using std::string;
using std::vector;

// GLog already implements symbolization. Just import their hidden symbol.
namespace google {
// Symbolizes a program counter.  On success, returns true and write the
// symbol name to "out".  The symbol name is demangled if possible
// (supports symbols generated by GCC 3.x or newer).  Otherwise,
// returns false.
bool Symbolize(void *pc, char *out, int out_size);
}

namespace kudu {

const int kPprofDefaultSampleSecs = 30; // pprof default sample time in seconds.

// pprof asks for the url /pprof/cmdline to figure out what application it's profiling.
// The server should respond by sending the executable path.
static void PprofCmdLineHandler(const Webserver::WebRequest& /*req*/,
                                Webserver::PrerenderedWebResponse* resp) {
  string executable_path;
  Env* env = Env::Default();
  WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path");
  resp->output << executable_path;
}

// pprof asks for the url /pprof/heap to get heap information. This should be implemented
// by calling HeapProfileStart(filename), continue to do work, and then, some number of
// seconds later, call GetHeapProfile() followed by HeapProfilerStop().
static void PprofHeapHandler(const Webserver::WebRequest& /*req*/,
                             Webserver::PrerenderedWebResponse* resp) {
  ostringstream* output = &resp->output;
#ifndef TCMALLOC_ENABLED
  *output << "%warn Heap profiling is not available without tcmalloc.\n";
#else
  // If we've started the process with heap profiling then dump the full profile.
  if (IsHeapProfilerRunning()) {
    char* profile = GetHeapProfile();
    *output << profile;
    free(profile);
    return;
  }

  // Otherwise dump the sample.
  string buf;
  MallocExtension::instance()->GetHeapSample(&buf);
  if (buf.find("This heap profile does not have any data") != string::npos) {
    // If sampling is disabled, tcmalloc prints a nice message with instructions
    // how to enable it. However, it only describes the environment variable method
    // and not our own gflag-based method, so we'll replace it with our own message.
    buf = "%warn\n"
        "%warn This heap profile does not have any data in it, because\n"
        "%warn the application was run with heap sampling turned off.\n"
        "%warn To obtain a heap sample, you must set the environment\n"
        "%warn variable TCMALLOC_SAMPLE_PARAMETER or the flag\n"
        "%warn --heap-sample-every-n-bytes to a positive sampling period,\n"
        "%warn such as 524288.\n"
        "%warn\n";
  }
  *output << buf;
#endif
}

// pprof asks for the url /pprof/profile?seconds=XX to get cpu-profiling information.
// The server should respond by calling ProfilerStart(), continuing to do its work,
// and then, XX seconds later, calling ProfilerStop().
static void PprofCpuProfileHandler(const Webserver::WebRequest& req,
                                   Webserver::PrerenderedWebResponse* resp) {
  ostringstream* output = &resp->output;
#ifndef TCMALLOC_ENABLED
  *output << "%warn CPU profiling is not available without tcmalloc.\n";
#else
  auto it = req.parsed_args.find("seconds");
  int seconds = kPprofDefaultSampleSecs;
  if (it != req.parsed_args.end()) {
    seconds = atoi(it->second.c_str());
  }
  // Build a temporary file name that is hopefully unique.
  string tmp_prof_file_name = strings::Substitute("/tmp/kudu_cpu_profile.$0.$1", getpid(), rand());
  ProfilerStart(tmp_prof_file_name.c_str());
  SleepFor(MonoDelta::FromSeconds(seconds));
  ProfilerStop();
  ifstream prof_file(tmp_prof_file_name.c_str(), std::ios::in);
  if (!prof_file.is_open()) {
    *output << "Unable to open cpu profile: " << tmp_prof_file_name;
    return;
  }
  *output << prof_file.rdbuf();
  prof_file.close();
#endif
}

// pprof asks for the url /pprof/growth to get heap-profiling delta (growth) information.
// The server should respond by calling:
// MallocExtension::instance()->GetHeapGrowthStacks(&output);
static void PprofGrowthHandler(const Webserver::WebRequest& /*req*/,
                               Webserver::PrerenderedWebResponse* resp) {
#ifndef TCMALLOC_ENABLED
  resp->output << "%warn Growth profiling is not available without tcmalloc.\n";
#else
  string heap_growth_stack;
  MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack);
  resp->output << heap_growth_stack;
#endif
}

// Lock contention profiling
static void PprofContentionHandler(const Webserver::WebRequest& req,
                                   Webserver::PrerenderedWebResponse* resp) {
  string secs_str = FindWithDefault(req.parsed_args, "seconds", "");
  int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), kPprofDefaultSampleSecs);
  int64_t discarded_samples = 0;

  MonoTime end = MonoTime::Now() + MonoDelta::FromSeconds(seconds);
  ostringstream profile;
  StartSynchronizationProfiling();
  while (MonoTime::Now() < end) {
    SleepFor(MonoDelta::FromMilliseconds(500));
    FlushSynchronizationProfile(&profile, &discarded_samples);
  }
  StopSynchronizationProfiling();
  FlushSynchronizationProfile(&profile, &discarded_samples);

  ostringstream* output = &resp->output;
  *output << "--- contention:" << endl;
  *output << "sampling period = 1" << endl;
  *output << "cycles/second = " << static_cast<int64_t>(base::CyclesPerSecond()) << endl;
  // pprof itself ignores this value, but we can at least look at it in the textual
  // output.
  *output << "discarded samples = " << discarded_samples << std::endl;
  *output << profile.str();

#if defined(__linux__)
  // procfs only exists on Linux.
  faststring maps;
  ReadFileToString(Env::Default(), "/proc/self/maps", &maps);
  *output << "--- Memory map: ---" << endl;
  *output << maps.ToString();
#endif // defined(__linux__)
}


// pprof asks for the url /pprof/symbol to map from hex addresses to variable names.
// When the server receives a GET request for /pprof/symbol, it should return a line
// formatted like: num_symbols: ###
// where ### is the number of symbols found in the binary. For now, the only important
// distinction is whether the value is 0, which it is for executables that lack debug
// information, or not-0).
//
// In addition to the GET request for this url, the server must accept POST requests.
// This means that after the HTTP headers, pprof will pass in a list of hex addresses
// connected by +, like:
//   curl -d '0x0824d061+0x0824d1cf' http://remote_host:80/pprof/symbol
// The server should read the POST data, which will be in one line, and for each hex value
// should write one line of output to the output stream, like so:
// <hex address><tab><function name>
// For instance:
// 0x08b2dabd    _Update
static void PprofSymbolHandler(const Webserver::WebRequest& req,
                               Webserver::PrerenderedWebResponse* resp) {
  if (req.request_method == "GET") {
    // Per the above comment, pprof doesn't expect to know the actual number of symbols.
    // Any non-zero value indicates that we support symbol lookup.
    resp->output << "num_symbols: 1";
    return;
  }

  int missing_symbols = 0;
  int invalid_addrs = 0;

  // Symbolization request.
  vector<StringPiece> pieces = strings::Split(req.post_data, "+");
  for (StringPiece p : pieces) {
    string hex_addr;
    if (!TryStripPrefixString(p, "0x", &hex_addr)) {
      invalid_addrs++;
      continue;
    }
    uint64_t addr;
    if (!safe_strtou64_base(hex_addr.c_str(), &addr, 16)) {
      invalid_addrs++;
      continue;
    }
    char symbol_buf[1024];
    if (google::Symbolize(reinterpret_cast<void*>(addr), symbol_buf, sizeof(symbol_buf))) {
      resp->output << p << "\t" << symbol_buf << std::endl;
    } else {
      missing_symbols++;
    }
  }

  LOG(INFO) << strings::Substitute(
      "Handled request for /pprof/symbol: requested=$0 invalid_addrs=$1 missing=$2",
      pieces.size(), invalid_addrs, missing_symbols);
}

void AddPprofPathHandlers(Webserver* webserver) {
  // Path handlers for remote pprof profiling. For information see:
  // https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html
  webserver->RegisterPrerenderedPathHandler("/pprof/cmdline", "", PprofCmdLineHandler,
                                            false, false);
  webserver->RegisterPrerenderedPathHandler("/pprof/heap", "", PprofHeapHandler, false, false);
  webserver->RegisterPrerenderedPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false);
  webserver->RegisterPrerenderedPathHandler("/pprof/profile", "", PprofCpuProfileHandler,
                                            false, false);
  webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false);
  webserver->RegisterPrerenderedPathHandler("/pprof/contention", "", PprofContentionHandler,
                                            false, false);
}

} // namespace kudu
